/*
   Copyright 2009 Abdulla Abdurakhmanov (abdulla@latestbit.com)
   Original sources are available at www.latestbit.com

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package org.latestbit.smartobjects.network;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;

import org.latestbit.smartobjects.utils.PostActionListenersContainer;

public abstract class ManagedConnection implements IManagedConnection {

	protected Collection<IManagedConnectionListener> connectionListeners = new LinkedList<IManagedConnectionListener>();
	protected PostActionListenersContainer<IManagedConnectionListener> palConnectionListeners = new PostActionListenersContainer<IManagedConnectionListener> ();
	
	protected Collection<IManagedStreamListener> streamListeners = new LinkedList<IManagedStreamListener>();
	protected PostActionListenersContainer<IManagedStreamListener> palStreamListeners = new PostActionListenersContainer<IManagedStreamListener> ();
	
	protected Set<ConnectionHandler> handlers = new HashSet<ConnectionHandler>();
	protected boolean connectionStatus = false;
	private ICoordinatorContext coordinatorContext;
	
	protected IEndpointLocation endpointLocation;
	protected IEndpointCoordinator endpointCoordinator;
	
	
	@Override
	public void addConnectionListener(IManagedConnectionListener listener) {
		synchronized(connectionListeners) {
			if(!this.palConnectionListeners.isActivated()) {
				connectionListeners.add(listener);
			}
			else
				palConnectionListeners.putListenerToAdd(listener);
		}
	}

	@Override
	public void delConnectionListener(IManagedConnectionListener listener) {
		synchronized(connectionListeners) {
			if(!this.palConnectionListeners.isActivated()) {			
				connectionListeners.remove(listener);
			}
			else
				palConnectionListeners.putListenerToRemove(listener);				
		}
	}
	
	@Override
	public void addStreamListener(IManagedStreamListener listener) {
		synchronized(streamListeners) {
			if(!this.palStreamListeners.isActivated()) {
				streamListeners.add(listener);
			}
			else
				palStreamListeners.putListenerToAdd(listener);
		}
	}

	@Override
	public void delStreamListener(IManagedStreamListener listener) {
		synchronized(streamListeners) {
			if(!this.palStreamListeners.isActivated()) {
				streamListeners.remove(listener);
			}
			else
				palStreamListeners.putListenerToRemove(listener);
		}
	}	
	
	@Override
	public boolean isOpen() {
		return connectionStatus;
	}
	
	@Override
	public ConnectionHandler getConnectionHandlerToSend() throws IOException {
		if(endpointCoordinator == null)
			throw new IOException("Unable to found any suitable connection (or coordinator) to send");
		return endpointCoordinator.getConnectionHandlerToSend(this);		
	}

	@Override
	public void send(ConnectionHandler handler, ByteBuffer buffer) throws IOException {		
		handler.getConnection().send( buffer );		
	}

	
	@Override
	public void assignConnectionHandler(ConnectionHandler handler) {
		synchronized(handlers) {
			handler.setManagedConnection(this);
			handlers.add(handler);
		}
	}

	@Override
	public void unassignConnectionHandler(ConnectionHandler handler) {
		synchronized(handlers) {
			handlers.remove(handler);
			handler.setManagedConnection(null);
		}
	}

	@Override
	public ICoordinatorContext getCoordinatorContext() {
		return this.coordinatorContext;
	}
	
	@Override
	public void setCoordinatorContext(ICoordinatorContext context) {
		this.coordinatorContext = context;
	}
	

	public void fireOnConnected() {
		synchronized(connectionListeners) {
			if(!connectionStatus) {
				connectionStatus = true;
				palConnectionListeners.activate();
				try {
					for(IManagedConnectionListener listener : connectionListeners) {
						listener.onConnected(this);
					}
				}
				finally {
					processingPostActionConnectionListeners();
				}
			}
		}
	}

	public void fireOnConnected(ConnectionHandler handler) {
		synchronized(connectionListeners) {
			palConnectionListeners.activate();
			try {
				for(IManagedConnectionListener listener : connectionListeners) {
					listener.onConnected(handler);
				}
			}
			finally {
				processingPostActionConnectionListeners();
			}
		}
	}

	public void fireOnConnectionFailure(String errorMessage) {
		synchronized(connectionListeners) {			
			connectionStatus = false;
			palConnectionListeners.activate();
			try {
				for(IManagedConnectionListener listener : connectionListeners) {
					listener.onConnectionFailure(this, errorMessage);
				}
			}
			finally {
				processingPostActionConnectionListeners();
			}				
		}
	}

	public void fireOnConnectionFailure(ConnectionHandler handler, String errorMessage) {
		synchronized(connectionListeners) {
			palConnectionListeners.activate();
			try {
				for(IManagedConnectionListener listener : connectionListeners) {
					listener.onConnectionFailure(handler, errorMessage);
				}
			}
			finally {
				processingPostActionConnectionListeners();
			}
		}
	}

	public void fireOnDisconnected() {
		synchronized(connectionListeners) {
			if(connectionStatus) {
				connectionStatus = false;
				palConnectionListeners.activate();
				try {
					for(IManagedConnectionListener listener : connectionListeners) {
						listener.onDisconnected(this);
					}
				}
				finally {
					processingPostActionConnectionListeners();
				}
			}
		}
	}

	public void fireOnDisconnected(ConnectionHandler handler) {
		synchronized(connectionListeners) {
			palConnectionListeners.activate();
			try {
				for(IManagedConnectionListener listener : connectionListeners) {
					listener.onDisconnected(handler);
				}
			}
			finally {
				processingPostActionConnectionListeners();
			}				
		}
	}

	public void fireOnReceiveError(ConnectionHandler handler,
			String errorMessage) {
		synchronized(streamListeners) {
			palStreamListeners.activate();
			try {
				for(IManagedStreamListener listener : streamListeners) {
					listener.onReceiveError(handler, errorMessage);
				}
			}
			finally {
				processingPostActionStreamListeners();
			}			
		}

	}

	public void fireOnReceived(ConnectionHandler handler, ByteBuffer buffer) {
		synchronized(streamListeners) {
			palStreamListeners.activate();
			try {			
				int curPos = buffer.position();
				int curLimit = buffer.limit();
				for(IManagedStreamListener listener : streamListeners) {				
					listener.onReceived(handler, buffer);
					buffer.position(curPos);
					buffer.limit(curLimit);
				}
			}
			finally {
				processingPostActionStreamListeners();
			}
		}
	}

	public void fireOnWriteError(ConnectionHandler handler, String errorMessage) {
		synchronized(streamListeners) {
			palStreamListeners.activate();
			try {						
				for(IManagedStreamListener listener : streamListeners) {
					listener.onWriteError(handler, errorMessage);
				}
			}
			finally {
				processingPostActionStreamListeners();
			}				
		}
	}
	
	protected void processingPostActionConnectionListeners() {
		palConnectionListeners.deactivate();
		try {
			for ( PostActionListenersContainer<IManagedConnectionListener>.PostActionQueueItem item : palConnectionListeners.getPostActionQueue() ) {
				if( item.isActionAdd() ) {
					addConnectionListener ( item.getListener() );
				}
				else
				if( item.isActionRemove() ) {
					delConnectionListener ( item.getListener() );
				}
			}
		}
		finally {
			palConnectionListeners.reset();
		}	
	}
	
	protected void processingPostActionStreamListeners() {
		palConnectionListeners.deactivate();
		try {
			for ( PostActionListenersContainer<IManagedStreamListener>.PostActionQueueItem item : palStreamListeners.getPostActionQueue() ) {
				if( item.isActionAdd() ) {
					addStreamListener ( item.getListener() );
				}
				else
				if( item.isActionRemove() ) {
					delStreamListener ( item.getListener() );
				}
			}
		}
		finally {
			palConnectionListeners.reset();
		}	
	}	
	
	@Override
	public synchronized IEndpointLocation getEndpointLocation() {
		return this.endpointLocation;
	}

}
